package com.lcj.ai.controller;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.text.CharSequenceUtil;
import com.alibaba.cloud.ai.tongyi.audio.api.Speech;
import com.alibaba.cloud.ai.tongyi.audio.api.SpeechResponse;
import com.lcj.ai.model.*;
import com.lcj.ai.service.AiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.image.Image;
import org.springframework.ai.image.ImageGeneration;
import org.springframework.ai.image.ImageResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;

import javax.annotation.processing.Completion;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/**
 * AI控制器，提供AI聊天、文生图等功能的接口。
 */
@Slf4j
@RestController
@RequestMapping("/ai")
@Tag(name = "ai聊天,文生图")
public class AiController {
    private final AiService aiService;

    /**
     * OSS文件存储路径，用于保存生成的语音文件和图片文件。
     */
    @Value("${spring.cloud.ai.oss.path}")
    private String ossPath;

    private final Environment environment;
    private final HttpServletRequest request;

    /**
     * AiController构造函数，初始化AI服务、环境变量和HTTP请求对象。
     *
     * @param aiService AI服务接口，用于提供AI相关功能的实现。
     * @param environment 环境变量对象，用于获取应用环境信息。
     * @param request HTTP请求对象，用于获取当前HTTP请求信息。
     */
    public AiController(AiService aiService, Environment environment, HttpServletRequest request) {
        this.aiService = aiService;
        this.environment = environment;
        this.request = request;
    }

    /**
     * 提供普通聊天功能，根据用户输入的问题返回答案。
     *
     * @param message 用户输入的聊天问题。
     * @return 包含聊天答案的结果对象。
     */
    @Operation(summary = "普通聊天")
    @Parameter(name = "message", description = "聊天问题", required = true, in = ParameterIn.QUERY)
    @GetMapping("/normal")
    public ResultDTO<AiResponseVO<String>> normalChat(@RequestParam("message") String message) {
        return ResultDTO.success(AiResponseVO.<String>builder().question(message).
                answer(this.aiService.normalCompletion(message)).build());
    }

    /**
     * 提供流式聊天功能，根据用户输入的问题返回一系列答案。
     *
     * @param message 用户输入的聊天问题。
     * @return 包含一系列聊天答案的Flux对象。
     */
    @Operation(summary = "流式聊天")
    @Parameter(name = "message", description = "聊天问题", required = true, in = ParameterIn.QUERY)
    @GetMapping("/stream")
    public Flux<ChatResponse> streamChat(@RequestParam("message") String message) {
        return this.aiService.streamCompletion(message);
    }

    /**
     * 根据用户提供的文本生成图片。
     *
     * @param image 包含生成图片所需的指令和配置的数据对象。
     * @return 如果生成成功，返回图片URL列表；否则返回失败结果。
     */
    @Operation(summary = "根据文本生成图片")
    @PostMapping("/image")
    public ResultDTO<AiResponseVO<List<String>>> generateImage(@RequestBody ImageOptionsDTO image) {
        ImageResponse resp = this.aiService.genImage(image.getInstructions(), image);
        List<ImageGeneration> results = resp.getResults();
        if(CollUtil.isEmpty(results)){
            return ResultDTO.fail("生成图片失败");
        }

        // 本地图片保存目录
        String localImageDir = CharSequenceUtil.format("{}image", ossPath);
        if(!FileUtil.exist(localImageDir)){
            FileUtil.mkdir(localImageDir);
        }

        List<String> imgUrls = new ArrayList<>(results.size());
        results.forEach(imageGen -> {
            Image img = imageGen.getOutput();
            byte[] imageBytes = Base64.getDecoder().decode(img.getB64Json());
            String imagePath = img.getUrl();
            // 从url中抽取图片名称
            String imageName = imagePath.substring(imagePath.lastIndexOf("/")+1, imagePath.indexOf("?"));
            // 保存图片到本地
            FileUtil.writeBytes(imageBytes, CharSequenceUtil.format("{}/{}", localImageDir,imageName));
            imgUrls.add(CharSequenceUtil.format("{}/image/{}", getHostPort(), imageName));
        });
        return ResultDTO.success(AiResponseVO.<List<String>>builder().question(image.getInstructions()).
                answer(imgUrls).build());
    }

    /**
     * 根据用户提供的文本生成语音。
     *
     * @param audio 包含生成语音所需的指令和配置的数据对象。
     * @return 如果生成成功，返回语音URL列表；否则返回失败结果。
     */
    @Operation(summary = "根据文本生成语音")
    @PostMapping("/audio")
    public ResultDTO<AiResponseVO<List<String>>> generateAudio(@RequestBody AudioOptionsDTO audio) {
        SpeechResponse resp = this.aiService.genAudio(audio.getInstructions(), audio);
        List<Speech> results = resp.getResults();
        if(CollUtil.isEmpty(results)){
            return ResultDTO.fail("生成语音失败");
        }
        // 本地语音保存目录
        String localAudioDir = CharSequenceUtil.format("{}audio", ossPath);
        if(!FileUtil.exist(localAudioDir)){
            FileUtil.mkdir(localAudioDir);
        }

        List<String> audioUrls = new ArrayList<>(results.size());
        results.forEach(speech -> {
            ByteBuffer buffer = speech.getOutput();
            // 生成随机文件名
            String audioName = CharSequenceUtil.format("{}.{}", UUID.randomUUID().toString(),audio.getFormat().name());
            FileUtil.writeBytes(buffer.array(), CharSequenceUtil.format("{}/{}", localAudioDir, audioName));
            audioUrls.add(CharSequenceUtil.format("{}/audio/{}", getHostPort(), audioName));
        });
        return ResultDTO.success(AiResponseVO.<List<String>>builder().question(audio.getInstructions()).
                answer(audioUrls).build());
    }

    /**
     * 获取服务的主机名和端口号。
     *
     * @return 包含主机名和端口号的字符串。
     */
    private String getHostPort(){
        // 端口
        String port = environment.getProperty("server.port", "8080");
        // 上下文
        String contextPath = environment.getProperty("server.servlet.context-path", "");
        if(CharSequenceUtil.isBlank(contextPath)){
            contextPath = "/";
        }else{
            if(!contextPath.startsWith("/")) contextPath = CharSequenceUtil.format("/{}", contextPath);
        }
        // 获取host
        String host = request.getRemoteHost();
        return CharSequenceUtil.format("http://{}:{}{}oss", host, port, contextPath);
    }

    /**
     * 解析模型输出，提供演员电影作品信息。
     *
     * @param actor 演员名称。
     * @return 包含演员电影作品信息的结果对象。
     */
    @Operation(summary = "模型输出解析OutputParser")
    @Parameter(name = "actor", description = "演员名称", required = true, in = ParameterIn.QUERY)
    @GetMapping("/outputParser")
    public ResultDTO<AiResponseVO<ActorFilms>> outputParser(@RequestParam("actor") String actor) {
        return ResultDTO.success(AiResponseVO.<ActorFilms>builder().question(actor).
                answer(this.aiService.getActorFilms(actor)).build());
    }

    /**
     * 使用Prompt Template生成与指定主题和类型相关的笑话。
     *
     * @param topic 笑话的主题。
     * @param adjective 笑话的类型。
     * @return 包含生成的笑话的结果对象。
     */
    @Operation(summary = "使用Prompt Template")
    @Parameter(name = "topic", description = "笑话的主题", required = true, in = ParameterIn.QUERY)
    @Parameter(name = "adjective", description = "笑话类型", required = true, in = ParameterIn.QUERY)
    @GetMapping("/genPromptTemplates")
    public ResultDTO<AiResponseVO<AssistantMessage>> genPromptTemplates(@RequestParam("topic") String topic,
                                                                        @RequestParam("adjective") String adjective) {
        return ResultDTO.success(AiResponseVO.<AssistantMessage>builder().question(CharSequenceUtil.
                        format("给我讲一个关于{}的{}笑话。", topic, adjective)).
                answer(this.aiService.genPromptTemplates(adjective,topic)).build());
    }

    /**
     * 让AI模型接入外部数据，根据消息和是否使用外部数据的标志返回模型的完成情况。
     *
     * @param message 消息内容。
     * @param outerData 是否使用外部数据。
     * @return 包含模型完成情况的结果对象。
     */
    @Operation(summary = "让AI模型接入外部数据（Prompt Stuff）")
    @Parameter(name = "message", description = "消息", required = true, in = ParameterIn.QUERY)
    @Parameter(name = "outerData", description = "是否使用外部数据", in = ParameterIn.QUERY)
    @GetMapping("/stuffCompletion")
    public ResultDTO<AiResponseVO<Completion>> stuffCompletion(
            @RequestParam("message") String message,
            @RequestParam(value = "outerData", defaultValue = "false") boolean outerData) {
        return ResultDTO.success(AiResponseVO.<Completion>builder().question(message).
                answer(this.aiService.stuffCompletion(message,outerData)).build());
    }

}

